﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Input;
using System.Windows;
using System.Diagnostics;

namespace Interop
{
	public class KeyboardHook : Hook
	{
		#region statics
		private static KeyboardHook _KeyboardHook;
		public static KeyboardHook Hook(PresentationSource activeSource)
		{
			if (_KeyboardHook == null)
				_KeyboardHook = new KeyboardHook(activeSource);
			return _KeyboardHook;
		}
		#endregion statics

		#region ctors
		public KeyboardHook(PresentationSource activeSource) : base(HookType.WH_KEYBOARD_LL, activeSource) 
		{
			HookProc = KeyboardHookProc;
		}
		#endregion ctors

		#region props
		protected override bool AllEventsAreNull
		{
			get
			{
				return	_KeyDown == null &&
						_KeyUp == null &&
						_SysKeyDown == null &&
						_SysKeyUp == null;
			}
		}
		#endregion props

		#region methods
		private void HandleKeyEvent(MessageForwarding test, KeyEventHandler handler, KeyboardDevice device, PresentationSource source, int timeStamp, Key key, ref bool handled, ref bool isKeyboardCode)
		{
			KeyEventArgs kea = null;
			if (CanForwardMessage(MessageForwarding, test))
			{
				isKeyboardCode = true;
				if (handler != null)
				{
					kea = new KeyEventArgs(Keyboard.PrimaryDevice, _ActiveSource, 0, key);
					kea.RoutedEvent = UIElement.KeyDownEvent;
					handler.Invoke(this, kea);
				}
			}
			handled = (kea != null) ? kea.Handled : false;
		}

		[DebuggerStepThrough]
		private IntPtr KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam)
		{
			IntPtr currHwnd = Process.GetCurrentProcess().Handle;
			if (currHwnd != _HostHwnd)
			{
				bool isKeyboadCode = false;
				bool handled = false;
				if (nCode >= 0)
				{
					try
					{
						KeyboardHookStruct khs = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
						Key key = KeyInterop.KeyFromVirtualKey(khs.VirtualKeyCode);
						switch ((Win32KeyboardCodes)wParam)
						{
							case Win32KeyboardCodes.WM_KEYDOWN:
								HandleKeyEvent(MessageForwarding.KeyDown | MessageForwarding.Both, _KeyDown, Keyboard.PrimaryDevice, _ActiveSource, 0, key, ref handled, ref isKeyboadCode);
								break;
							case Win32KeyboardCodes.WM_KEYUP:
								HandleKeyEvent(MessageForwarding.KeyUp | MessageForwarding.Both, _KeyUp, Keyboard.PrimaryDevice, _ActiveSource, 0, key, ref handled, ref isKeyboadCode);
								break;
							case Win32KeyboardCodes.WM_SYSKEYDOWN:
								HandleKeyEvent(MessageForwarding.KeyDown | MessageForwarding.Both, _SysKeyDown, Keyboard.PrimaryDevice, _ActiveSource, 0, key, ref handled, ref isKeyboadCode);
								break;
							case Win32KeyboardCodes.WM_SYSKEYUP:
								HandleKeyEvent(MessageForwarding.KeyUp | MessageForwarding.Both, _SysKeyUp, Keyboard.PrimaryDevice, _ActiveSource, 0, key, ref handled, ref isKeyboadCode);
								break;
						}
						if (UnhandledToHost && isKeyboadCode && !handled)
							handled = NativeMethods.PostMessage(_HostHwnd, (uint)wParam, (IntPtr)khs.VirtualKeyCode, (IntPtr)khs.ScanCode);
					}
					catch (Exception ex)
					{
						Debug.WriteLine(ex.Message);
					}
				}
				if (handled)
					return (IntPtr)(-1);
			}
			return NativeMethods.CallNextHookEx(_hHook, nCode, wParam, lParam);
		}
		#endregion methods

		#region events
		private event KeyEventHandler _KeyDown;
		public event KeyEventHandler KeyDown
		{
			add
			{
				EnsureSubscriptionToEvents();
				_KeyDown += value;
			}
			remove
			{
				_KeyDown -= value;
				TryUnsubscribeFromEvents();
			}
		}
		private event KeyEventHandler _KeyUp;
		public event KeyEventHandler KeyUp
		{
			add
			{
				EnsureSubscriptionToEvents();
				_KeyUp += value;
			}
			remove
			{
				_KeyUp -= value;
				TryUnsubscribeFromEvents();
			}
		}
		private event KeyEventHandler _SysKeyDown;
		public event KeyEventHandler SysKeyDown
		{
			add
			{
				EnsureSubscriptionToEvents();
				_SysKeyDown += value;
			}
			remove
			{
				_SysKeyDown -= value;
				TryUnsubscribeFromEvents();
			}
		}
		private event KeyEventHandler _SysKeyUp;
		public event KeyEventHandler SysKeyUp
		{
			add
			{
				EnsureSubscriptionToEvents();
				_SysKeyUp += value;
			}
			remove
			{
				_SysKeyUp -= value;
				TryUnsubscribeFromEvents();
			}
		}
		#endregion events

		#region fields
		
		#endregion fields
	}
}
